<?php

namespace App\Services;

use Illuminate\Http\UploadedFile; // 用于类型提示上传的文件对象
use Illuminate\Support\Facades\Config;
use Illuminate\Support\Facades\Log;
use OSS\OssClient;
use OSS\Core\OssException;

class OssStorageService
{
    protected $ossClient;
    protected $bucket;
    protected $ossUrl;

    /**
     * 构造函数，注入依赖项。
     *
     * @param OssClient $ossClient
     */
    public function __construct(OssClient $ossClient)
    {
        $this->ossClient = $ossClient;
        $this->bucket = Config::get('oss.bucket');
        $this->ossUrl = Config::get('oss.url'); // 获取 OSS 访问 URL

        if (empty($this->bucket)) {
            throw new \InvalidArgumentException('OSS Bucket 配置缺失。');
        }
    }

    /**
     * 上传文件到 OSS。
     *
     * @param UploadedFile $file 用户上传的文件对象
     * @param string|null $directory 自定义上传的基础目录 (可选, 默认为 'uploads')
     * @return array 包含 object_name 和 url 的数组
     * @throws OssException 如果 OSS 操作失败
     * @throws \Exception 如果发生其他错误 (例如文件读取失败)
     */
    public function uploadFile(UploadedFile $file, ?string $directory = 'uploads'): array
    {
        // 获取当前环境
        $environment = app()->environment();
        // 构建基础路径，包含环境名和可选的自定义目录
        $basePath = trim($directory ?: 'uploads', '/') . '/' . $environment . '/'; // 例如: uploads/production/
        // 构建日期路径
        $datePath = date('Ymd') . '/'; // 例如: 20231027/
        // 生成唯一文件名部分
        $fileName = uniqid() . '_' . $file->getClientOriginalName();

        // 完整的 OSS 对象名称
        $objectName = $basePath . $datePath . $fileName;

        // 获取文件的本地临时路径
        $filePath = $file->getRealPath();

        try {
            // 调用 OSS SDK 上传文件
            $result = $this->ossClient->putObject($this->bucket, $objectName, file_get_contents($filePath));

            // 生成公开访问 URL (如果配置了 OSS_URL)
            $publicUrl = null;
            if ($this->ossUrl) {
                $publicUrl = rtrim($this->ossUrl, '/') . '/' . ltrim($objectName, '/');
            }

            // 返回包含对象名称和 URL 的信息
            return [
                'object_name' => $objectName,
                'url' => $publicUrl,
                'oss_info' => $result, // 可以选择性返回 OSS 的原始信息
            ];

        } catch (OssException $e) {
            Log::error('OSS 服务上传失败: ' . $e->getMessage(), ['exception' => $e]);
            // 将 OssException 重新抛出，由调用方 (Controller) 处理具体的 HTTP 响应
            throw $e;
        } catch (\Exception $e) {
            Log::error('文件服务上传失败: ' . $e->getMessage(), ['exception' => $e]);
            // 将其他异常也重新抛出
            throw $e;
        }
    }

    /**
     * 从 OSS 删除文件。
     *
     * @param string $objectName 要删除的文件的完整对象名称 (例如: uploads/production/20231027/xyz_image.jpg)
     * @return bool 返回 true 表示成功 (OSS SDK 的 deleteObject 在成功时不返回特定值，所以我们返回 true)
     * @throws OssException 如果 OSS 操作失败
     * @throws \InvalidArgumentException 如果 $objectName 为空
     */
    public function deleteFile(string $objectName): bool
    {
        if (empty($objectName)) {
            throw new \InvalidArgumentException('对象名称不能为空。');
        }

        try {
            // 调用 OSS SDK 的 deleteObject 方法
            $this->ossClient->deleteObject($this->bucket, $objectName);
            // 记录删除成功的日志 (可选)
            Log::info('OSS 文件删除成功。 ', ['bucket' => $this->bucket, 'object_name' => $objectName]);
            return true; // 表示删除操作已成功执行

        } catch (OssException $e) {
            Log::error('OSS 删除失败: ' . $e->getMessage(), [
                'bucket' => $this->bucket,
                'object_name' => $objectName,
                'exception' => $e
            ]);
            // 重新抛出异常，以便调用方知道操作失败
            throw $e;
        }
        // 注意：如果文件不存在，deleteObject 通常不会抛出异常，这也是符合预期的行为。
        // 如果需要明确知道文件是否存在后再删除，可以先调用 doesObjectExist 方法。
    }

    /**
     * 通过文件的完整 URL 从 OSS 删除文件。
     *
     * @param string $fileUrl 要删除的文件的完整公开访问 URL。
     *                        (例如: https://your-bucket.oss-cn-hangzhou.aliyuncs.com/uploads/production/20231027/xyz_image.jpg)
     * @return bool 返回 true 表示成功。
     * @throws OssException 如果 OSS 操作失败。
     * @throws \InvalidArgumentException 如果 URL 无效、为空、不匹配配置的 OSS_URL，或者 OSS_URL 未配置。
     */
    public function deleteFileByUrl(string $fileUrl): bool
    {
        if (empty($fileUrl)) {
            throw new \InvalidArgumentException('文件 URL 不能为空。');
        }

        // 检查 OSS_URL 是否已配置
        if (empty($this->ossUrl)) {
            throw new \InvalidArgumentException('OSS_URL 未配置，无法通过 URL 删除。');
        }

        // 确保配置的 OSS_URL 以斜杠结尾，方便比较
        $configuredBaseUrl = rtrim($this->ossUrl, '/') . '/';

        // 检查传入的 URL 是否以配置的基础 URL 开头
        if (!str_starts_with($fileUrl, $configuredBaseUrl)) {
             throw new \InvalidArgumentException('提供的 URL 与配置的 OSS_URL 前缀不匹配。');
        }

        // 从 URL 中提取 objectName (移除基础 URL 部分)
        $objectName = substr($fileUrl, strlen($configuredBaseUrl));

        // 如果提取出的 objectName 为空 (例如 URL 就是基础 URL)，则无效
        if ($objectName === '' || $objectName === false) {
             throw new \InvalidArgumentException('无法从提供的 URL 中提取对象名称。');
        }

        // 现在我们有了 objectName，调用现有的 deleteFile 方法
        return $this->deleteFile($objectName);
    }

    // 未来可以添加其他方法，例如: generateSignedUrl 等
    // public function generateSignedUrl(string $objectName, int $timeout = 3600) { ... }
} 